#include "vm/page.h"
#include <debug.h>
#include <inttypes.h>
#include <round.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "userprog/gdt.h"
#include "userprog/pagedir.h"
#include "userprog/tss.h"
#include "filesys/directory.h"
#include "filesys/file.h"
#include "filesys/filesys.h"
#include "threads/flags.h"
#include "threads/init.h"
#include "threads/interrupt.h"
#include "threads/palloc.h"
#include "threads/thread.h"
#include "threads/synch.h"
#include "threads/vaddr.h"
#include "threads/malloc.h"

#include "vm/frame.h"
#include "vm/swap.h"

bool page_init()
{
	struct thread *cur = thread_current();
	cur->pages = (struct hash *) malloc (sizeof(struct hash));
	if(cur->pages == NULL)
		return false;

	bool success = hash_init (cur->pages, page_hash, page_less, NULL);
	if (success){
		lock_init (&cur->page_lock);
		return true;
	}
	else
	{
		free (cur->pages);
		return false;
	}
	//hash_lock
}

void page_deallocate(struct thread *t)
{
	lock_acquire(&t->page_lock);
	hash_destroy(t->pages,hash_destructor);
	lock_release(&t->page_lock);

	free(t->pages);
}

bool lazy_page_insert(void *upage, bool writable, struct file *f, int32_t offset, int readbyte){
	struct page *_p = (struct page *)malloc(sizeof(struct page));
	if(_p == NULL)
		return false;
	struct thread *cur = thread_current();
	bool ret = true;
	lock_acquire(&cur->page_lock);

	_p->upage = upage;
	_p->t = cur;
	_p->swap_position = -1;
	_p->writable = writable;
	_p->fp = f;
	_p->offset = offset;
	_p->readbyte = readbyte;
	_p->isLoaded = false;

	struct hash_elem *h_elem = hash_find(cur ->pages, &_p->hash_elem);
	if(h_elem != NULL)
	{
		ret=false;
		free(_p);
	}
	else{
		h_elem =hash_insert(cur->pages,&_p->hash_elem);
		if ( h_elem!=NULL)
		{
			ret=false;
			free(_p);
		}
	}
	lock_release(&cur->page_lock);
	return ret;
}

bool page_insert(void *upage, void *kpage, bool writable)
{
	struct page *_p = (struct page *)malloc(sizeof(struct page));
	if(_p==NULL)
		return false;
	struct thread *cur = thread_current();
	bool ret = true;
	lock_acquire(&cur->page_lock);

	_p->upage = upage;
	_p->t = cur;
	_p->swap_position = -1;
	_p->fp = NULL;
	_p->offset = -1;
	_p->readbyte = -1;
	_p->writable = writable;
	_p->isLoaded = true;

	struct hash_elem *h_elem = hash_find(cur ->pages, &_p->hash_elem);
	if (h_elem != NULL)	// page is in swap disk or in frame
	{
		free (_p);	// so remove newly created one
		_p = hash_entry(h_elem, struct page,hash_elem);	// figure out where it is
		if( _p != NULL)
		{
			void *got_page = pagedir_get_page(_p->t->pagedir, _p->upage);
			if ( got_page !=NULL) // already in frame
			{
				ret = false;
			}
			else	//in swap disk, so need to remove from disk first
			{
				// be careful about race condition
				swap_remove_page(_p);

				///////////////????????????????????????????????????????????????
				_p->isLoaded = true;
			}
		}
		else{	// something wrong... just return false
			ret = false;
		}
	}
	else	// it is not created before
	{
		// so push it to the supplement page table
		h_elem =hash_insert(cur->pages,&_p->hash_elem);

		// if something weird situation happend, then don't try to do more.
		if ( h_elem!=NULL)
		{
			ret=false;
			free(_p);
		}

	}

	// now _p has a page that currently not in frame
	if (ret)
	{
		if(! pagedir_set_page( _p->t->pagedir, upage, kpage, writable))
		{
			// failed to set a page
			free(_p);
			ret = false;
		}
		else
			// if successed
			pagedir_set_accessed(_p->t->pagedir,upage,true);

	}
	lock_release(&cur->page_lock);
	return ret;

}



void hash_destructor(struct hash_elem *h_elem,void *aux UNUSED )
{
	struct page *_p= hash_entry(h_elem, struct page,hash_elem);
	if(_p->swap_position!=-1){
		swap_remove_page(_p);
	}
	free(_p);
}

/* Returns a hash value for page p. */
	unsigned
page_hash (const struct hash_elem *p_, void *aux UNUSED)
{
	const struct page *p = hash_entry (p_, struct page, hash_elem);
	return hash_bytes (&p->upage, sizeof p->upage);
}

/* Returns true if page a precedes page b. */
	bool
page_less (const struct hash_elem *a_, const struct hash_elem *b_,
		void *aux UNUSED)
{
	const struct page *a = hash_entry (a_, struct page, hash_elem);
	const struct page *b = hash_entry (b_, struct page, hash_elem);
	return a->upage < b->upage;
}

/* Returns the page containing the given virtual address,
 * or a null pointer if no such page exists. */
	struct page *
page_lookup (void *upage, struct thread *t)
{
	if(is_kernel_vaddr(upage))
		return NULL;

	if(t==NULL)
		t = thread_current();

	struct page p;
	struct hash_elem *e;

	lock_acquire(&t->page_lock);

	p.upage = upage;
	e = hash_find (t->pages, &p.hash_elem);

	lock_release(&t->page_lock);

	return e != NULL ? hash_entry (e, struct page, hash_elem) : NULL;
}
